library(tidyverse)
library(rlang)
library(lubridate)
library(scales)
library(ggrepel)
library(glue)
library(rvest)
library(pander)
library(plotly)
library(QuantTools)
panderOptions("big.mark", ",")
panderOptions("table.split.table", Inf)
panderOptions("table.style", "rmarkdown")
panderOptions("missing", "")
theme_set(theme_bw())
auStates <- c(
  ACT = "Australian Capital Territory",
  QLD = "Queensland",
  NSW = "New South Wales",
  VIC = "Victoria",
  SA = "South Australia",
  WA = "Western Australia",
  NT = "Northern Territory",
  TAS = "Tasmania"
)

Data Sources

getCovidTable <- function(
  state = c("ACT", "QLD", "NSW", "VIC", "SA", "WA", "NT", "TAS"),
  type = c("cases", "recoveries", "deaths", "active-cases", "tests"),
  .state_full = auStates
){
  
  state <- match.arg(state)
  
  type <- match.arg(type)
  type_name <- c(
    cases = "confirmed",
    recoveries = "recovered",
    deaths = "deaths",
    `active-cases` = "active",
    tests = "tests"
  )[[type]]
  col_name <- c(
    cases = "CASES",
    recoveries = "RECOV",
    deaths = "DEATHS",
    `active-cases` = "ACTIVE",
    tests = "TESTS"
  )[[type]]
  
  url <- glue("https://covidlive.com.au/report/daily-{type}/{str_to_lower(state)}")
  url %>%
    read_html() %>%
    html_nodes("body") %>%
    xml_find_all(
      glue("//table[contains(@class, 'DAILY-{str_to_upper(type)}')]")
    ) %>% 
    html_table() %>%
    .[[1]] %>%
    as_tibble() %>%
    dplyr::select(date = DATE, !!sym(type_name) := !!sym(col_name)) %>%
    separate(date, into = c("day", "month")) %>%
    mutate(
      year = year(today()),
      date = paste(year, month, day, sep = "-"),
      date = parse_date_time(date, orders = "%Y-%B-%d"),
      date = ymd(date),
      State = .state_full[str_to_upper(state)],
      Country = "Australia"
    ) %>%
    mutate_at(
      .vars = type_name, 
      .funs = function(x){as.integer(str_remove_all(x, ","))}
    ) %>%
    dplyr::select(State, Country, date, !!sym(type_name)) %>%
    arrange(date) 
}
confirmed <- names(auStates) %>%
  lapply(getCovidTable, type = "cases") %>%
  bind_rows()
recovered <- names(auStates) %>%
  lapply(getCovidTable, type = "recoveries") %>%
  bind_rows()
deaths <- names(auStates) %>%
  lapply(getCovidTable, type = "deaths") %>%
  bind_rows()
active <- names(auStates) %>%
  lapply(getCovidTable, type = "active-cases") %>%
  bind_rows()
tested <- names(auStates) %>%
  lapply(getCovidTable, type = "tests") %>%
  bind_rows() %>%
  dplyr::filter(!is.na(tests))
getLocal <- function(  
  state = c("ACT", "QLD", "NSW", "VIC", "SA", "WA", "NT", "TAS"),  
  .state_full = auStates
){
  
  state <- match.arg(state)
  
  url <- glue("https://covidlive.com.au/report/daily-source-overseas/{str_to_lower(state)}")
  url %>%
    read_html() %>%
    html_nodes("body") %>%
    xml_find_all("//table[contains(@class, 'DAILY-SOURCE-OVERSEAS')]") %>% 
    html_table() %>%
    .[[1]] %>%
    as_tibble() %>%
    dplyr::select(date = DATE, local = LOCAL) %>%
    separate(date, into = c("day", "month")) %>%
    mutate(
      year = year(today()),
      date = paste(year, month, day, sep = "-"),
      date = parse_date_time(date, orders = "%Y-%B-%d"),
      date = ymd(date),
      State = .state_full[str_to_upper(state)],
      Country = "Australia",
      local = str_remove_all(local, ","),
      local = as.integer(local)
    ) %>%
    dplyr::select(State, Country, date, local) %>%
    arrange(date) 
}
local <- names(auStates) %>%
  lapply(getLocal) %>%
  bind_rows() %>%
  dplyr::filter(!is.na(local))
dt <- max(confirmed$date)
if (hour(Sys.time()) < 9) dt <- dt -1
dt_char <- as.character(dt)
vic_aged <- "https://covidlive.com.au/report/daily-outbreaks-aged" %>%
  read_html() %>%
  html_nodes("body") %>%
  xml_find_all("//table[contains(@class, 'DAILY-OUTBREAKS-AGED')]") %>% 
    html_table() %>%
    .[[1]] %>%
    as_tibble() %>%
    dplyr::select(date = DATE, deaths = DEATHS, active = ACTIVE) %>%
    separate(date, into = c("day", "month")) %>%
    mutate(
      year = year(today()),
      date = paste(year, month, day, sep = "-"),
      date = parse_date_time(date, orders = "%Y-%b-%d"),
      date = ymd(date),
      State = "Victoria (Aged Care)",
      Country = "Australia"
    ) %>%
  arrange(date) %>%
  mutate(
    deaths = na_locf(deaths),
    active = na_locf(active),
    new = c(active[[1]], diff(active)),
    new = ifelse(new < 0, 0, new),
    confirmed = cumsum(new),
    recovered = confirmed - deaths - active,
    recovered = ifelse(recovered < 0, 0, recovered)
  ) %>%
  dplyr::filter(date <= dt) %>%
  dplyr::select(
    State, Country, date, deaths, confirmed, active, recovered
  ) 

Data for confirmed cases, active cases, recoveries and fatalities was exclusively sourced from COVID LIVE. Similarly, data for the Victorian Aged Care outbreak was obtained from data based on press releases

International Data

International data and figures can be viewed here

Latest Australian Data

ausPops <- tribble(
  ~State, ~Population,
  "New South Wales",    8117976,
  "Victoria", 6629870,
  "Queensland", 5115451,
  "South Australia", 1756494,
  "Western Australia", 2630557,
  "Tasmania", 535500,
  "Northern Territory", 245562,
  "Australian Capital Territory", 428060
)

Australian State populations were taken from the ABS Website and were accurate in Sept 2019.

  • Using an estimated population size of 25,459,470, the total percentage of the Australian population confirmed as having been infected currently sits at 0.11%, or one person in every 900.
  • Within Victoria, that rises to one in every 326 having contracted the virus at some point
confirmed %>% 
  left_join(recovered, by = c("State", "Country", "date")) %>%
  left_join(deaths, by = c("State", "Country", "date")) %>%
  left_join(active, by = c("State", "Country", "date")) %>%
  bind_rows(vic_aged) %>%
  dplyr::filter(
    date %in% c(dt, dt -1)
  ) %>%
  mutate(
    confirmed = case_when(
      State == "Victoria" ~ confirmed - dplyr::filter(., State == "Victoria (Aged Care)")$confirmed,
      TRUE ~ confirmed
    ),
    recovered = case_when(
      State == "Victoria" ~ recovered - dplyr::filter(., State == "Victoria (Aged Care)")$recovered,
      TRUE ~ recovered
    ),
    deaths = case_when(
      State == "Victoria" ~ deaths - dplyr::filter(., State == "Victoria (Aged Care)")$deaths,
      TRUE ~ deaths
    ),
    active = case_when(
      State == "Victoria" ~ active - dplyr::filter(., State == "Victoria (Aged Care)")$active,
      TRUE ~ active
    ),
    State = str_replace_all(State, "^Victoria$", "Victoria (Non-Aged Care)")
  ) %>%
  group_by(State) %>%
  mutate(
    Increase = c(NA, diff(confirmed)),
    `% Increase` = Increase / min(confirmed),
    recovered = c(NA, max(recovered)),
    deaths = c(NA, max(deaths)),
    active = c(NA, active[2])
  ) %>%
  ungroup() %>%
  pivot_wider(
    id_cols = State, 
    names_from = date, 
    values_from = c(confirmed, recovered, deaths, active, Increase, `% Increase`)
  ) %>%
  dplyr::select_if(function(x){sum(is.na(x)) <= 1}) %>%
  rename_all(
    str_remove_all, pattern = "confirmed_"
  ) %>%
  rename_all(str_remove_all, pattern = "_202[01].+") %>%
  arrange(State) %>%
  bind_rows(
    tibble(
      State = "National Total",
      "{as.character(dt -1)}" := sum(.[[as.character(dt -1)]]),
      "{dt_char}" := sum(.[[dt_char]]),
      Increase = sum(.$Increase),
      recovered = sum(.$recovered),
      deaths = sum(.$deaths),
      active = sum(.$active),
      `% Increase` = Increase / !!sym(as.character(dt - 1))
    )
  ) %>%
  mutate(
    `% Increase` = percent(`% Increase`, accuracy = 0.01),
    `Fatality Rate` = percent(deaths / !!sym(dt_char), accuracy = 0.1),
    `Recovery Rate` = percent(recovered / !!sym(dt_char), accuracy = 0.1),
  ) %>%
  rename(
    Fatalities = deaths,
    Recovered = recovered,
    `Currently Active` = active
  ) %>%
  dplyr::select(
    State, starts_with("20"), ends_with("Increase"), starts_with("Fatal"), starts_with("Recov"), `Currently Active`
  ) %>%
  pander(
    justify = "lrrrrrrrrr",
    caption = paste(
      "*Confirmed cases, fatalities and recoveries reported by each state at time of preparation.",
      "Given that the Victorian outbreak significantly impacted the Federally-run Aged Care facilities,", 
      "Victorian statistics are broken down into those for which the Andrews government is responsible (Non-Aged Care),", 
      "and those __for which the Morrisson government is reponsible (Aged Care)__.", 
      "Detailed statistics on Aged Care are not currently available for other states.*"
    ),
    emphasize.strong.rows = nrow(.)
  )
Confirmed cases, fatalities and recoveries reported by each state at time of preparation. Given that the Victorian outbreak significantly impacted the Federally-run Aged Care facilities, Victorian statistics are broken down into those for which the Andrews government is responsible (Non-Aged Care), and those for which the Morrisson government is reponsible (Aged Care). Detailed statistics on Aged Care are not currently available for other states.
State 2020-12-24 2020-12-25 Increase % Increase Fatalities Fatality Rate Recovered Recovery Rate Currently Active
Australian Capital Territory 118 118 0 0.00% 3 2.5% 114 96.6% 1
New South Wales 4,823 4,832 9 0.19% 53 1.1% 3,192 66.1% 119
Northern Territory 73 73 0 0.00% 0 0.0% 62 84.9% 11
Queensland 1,238 1,240 2 0.16% 6 0.5% 1,216 98.1% 12
South Australia 568 570 2 0.35% 4 0.7% 562 98.6% 4
Tasmania 234 234 0 0.00% 13 5.6% 221 94.4% 0
Victoria (Aged Care) 2,128 2,128 0 0.00% 652 30.6% 1,476 69.4% 0
Victoria (Non-Aged Care) 18,233 18,233 0 0.00% 168 0.9% 18,051 99.0% 10
Western Australia 847 848 1 0.12% 9 1.1% 832 98.1% 7
National Total 28,262 28,276 14 0.05% 908 3.2% 25,726 91.0% 164

Plot of Current Australian Values

ausStatsCap <- "*Current confirmed and recovered cases, along with fatalities for Australia only. Active cases are shown as confirmed cases excluding fatalities and those classed as recovered. Loess curves through all points are shown as continuous lines. Data is only shown from 1^st^ April 2020 as this was the first date of complete data being available. Recovered patient information was also sparse in the early stages of data collection, and as a result estimates of active infections will be a significant underestimate until 6^th^ April. In particular, QLD only began reporting recovered cases on this date. NSW followed a fortnight after this date and as such, only the most recent numbers can be considered as accurate. Below this plot, the same figures can be seen broken down by state.*"
ggplotly(
  confirmed %>%
    dplyr::filter(date <= dt) %>%
    left_join(deaths, by = c("State", "Country", "date")) %>%
    left_join(recovered, by = c("State", "Country", "date")) %>%
    left_join(active, by = c("State", "Country", "date")) %>%
    mutate_at(vars(confirmed, deaths, recovered, active), na_locf) %>%
    group_by(Country, date) %>%
    summarise_at(vars(confirmed, deaths, recovered, active), sum) %>%
    ungroup() %>%
    pivot_longer(
      cols = c(active, confirmed, deaths, recovered),
      names_to = "Status",
      values_to = "Total"
    ) %>%
    arrange(Status, date) %>%
    dplyr::filter(date > ymd("2020-04-01")) %>%
    mutate(
      Status = str_to_title(Status),
      Status = str_replace_all(Status, "Deaths", "Fatal"),
      Status = factor(Status, levels = c("Fatal", "Recovered", "Active"))
    ) %>%
    dplyr::filter(Total > 0) %>%
    rename_all(str_to_title) %>%
    dplyr::filter(Status != "Confirmed") %>%
    ggplot(aes(Date, Total, fill = Status)) +
    geom_col() +
    geom_line(
      data = . %>%
        group_by(Date) %>%
        summarise(
          Total = sum(Total)
        ) %>%
        mutate(Status = "Confirmed"),
      colour = "blue"
    ) +
    scale_fill_manual(
      values = c(
        Active = rgb(0, 0, 0),
        Confirmed = rgb(0, 0.3, 0.7),
        Fatal = rgb(0.8, 0.2, 0.2),
        Recovered = rgb(0.2, 0.7, 0.4)
      )
    ) +
    scale_x_date(expand = expansion(c(0, 0.03))) +
    scale_y_continuous(expand = expansion(c(0, 0.05))) +
    labs("Total Cases")
)

Current confirmed and recovered cases, along with fatalities for Australia only. Active cases are shown as confirmed cases excluding fatalities and those classed as recovered. Loess curves through all points are shown as continuous lines. Data is only shown from 1st April 2020 as this was the first date of complete data being available. Recovered patient information was also sparse in the early stages of data collection, and as a result estimates of active infections will be a significant underestimate until 6th April. In particular, QLD only began reporting recovered cases on this date. NSW followed a fortnight after this date and as such, only the most recent numbers can be considered as accurate. Below this plot, the same figures can be seen broken down by state.

ggplotly(
  confirmed %>%
    dplyr::filter(date <= dt) %>%    
    left_join(deaths, by = c("State", "Country", "date")) %>%
    left_join(recovered, by = c("State", "Country", "date")) %>%
    left_join(active, by = c("State", "Country", "date")) %>%
    mutate_at(vars(confirmed, deaths, recovered, active), na_locf) %>%
    dplyr::filter(
      date > ymd("2020-04-01"),
    ) %>%
    arrange(date) %>%
    left_join(ausPops) %>%
    pivot_longer(
      cols = c(confirmed, deaths, recovered, active),
      names_to = "status",
      values_to = "count"
    ) %>%
    dplyr::filter(
      count > 0,
      !(State %in% c("Queensland", "New South Wales") & status == "recovered" & date < ymd("2020-04-06")),
      !(State %in% c("South Australia") & status == "recovered" & date < ymd("2020-04-01")),    
      !(State %in% c("Tasmania") & status == "recovered" & date < ymd("2020-04-02")),
    ) %>%
    dplyr::filter(status != "confirmed") %>%
    mutate(
      rate = 1e6*count/Population,
      rate = round(rate, 2),
      status = str_replace(status, "deaths", "fatal") %>% str_to_title(),
      status = factor(status, levels = c("Fatal", "Recovered", "Active"))
    ) %>%
    rename_all(str_to_title) %>%
    ggplot(aes(Date, Rate, fill = Status, label = Count)) +
    geom_col() +
    geom_line(
      data = . %>%
        group_by(State, Date) %>%
        summarise(
          Rate = sum(Rate),
          Count = sum(Count)
        ) %>%
        mutate(Status = "Confirmed"),
      colour = "blue"
    ) +
    facet_wrap(~State, ncol = 4) + 
    scale_fill_manual(
      values = c(
        Active = rgb(0, 0, 0),
        Confirmed = rgb(0, 0.3, 0.7),
        Fatal = rgb(0.8, 0.2, 0.2),
        Recovered = rgb(0.2, 0.7, 0.4)
      )
    ) +
    scale_x_date(expand = expansion(c(0, 0.03))) +
    labs(y = "Rate (Cases / Million)")
)

Breakdown of individual states. Victorian recovered numbers began to be accurately reported from 22nd March, with other states gradually providing this information. NSW/QLD recovered cases have only recently begun being reported and up until the most recent dates, recovered/active values were very approximate for these states. The extreme drop for NSW active cases in early June is a function of the changed reporting strategy implemented by NSW Health.

Daily New Cases

ggplotly(
  confirmed %>%
    dplyr::filter(date <= dt) %>%    
    group_by(State) %>%
    mutate(daily = c(0, diff(confirmed))) %>%
    ungroup() %>%
    dplyr::filter(confirmed > 0) %>%
    mutate(
      daily = case_when(
        daily < 0 ~ 0,
        daily >= 0 ~ daily
      )
    ) %>%
    bind_rows(
      group_by(., date) %>%
        summarise(daily = sum(daily)) %>%
        ungroup() %>%
        mutate(State = "All States")
    ) %>%
    group_by(State) %>%
    mutate(
      MA = round(sma(daily, 7), 2),
      MA2 = round(sma(daily, 14), 2),
      `Above Average` = MA > MA2
    ) %>%
    dplyr::filter(date > "2020-03-01") %>%
    ggplot(aes(date, daily)) +
    geom_col(
      aes(fill = `Above Average`, colour = `Above Average`),
      data = . %>% dplyr::filter(!is.na(`Above Average`)),
      width = 1/2
    ) +
    geom_line(aes(y = MA), colour = "blue") +
    geom_line(aes(y = MA2), colour = "black") +
    facet_wrap(~State, scales = "free_y") +
    labs(
      x = "Date",
      y = "Daily New Cases",
      fill = "\nAbove\nAverage"
    ) +
    scale_fill_manual(values = c("white", rgb(1, 0.2, 0.2))) +
    scale_colour_manual(values = c("grey50", rgb(1, 0.2, 0.2))),
  tooltip = c(
    "date", "daily", "MA"
  )
)

Daily new cases for each state shown against the 7-day (blue) and 14-day (black) averages. Days which the 7-day average is above the 14-day average are highlighted in red.

Australian Fatality Rate

inc <- 6
icu <- 11
d <- 7
offset <- icu + d 
minDate <- "2020-04-20"
list(
  confirmed %>%
    dplyr::filter(date <= dt) %>%    
    group_by(date) %>%
    summarise_at("confirmed", sum) %>%
    left_join(
      deaths %>%
        group_by(date) %>%
        summarise_at("deaths", sum)
    ) %>%
    dplyr::filter(
      date > minDate
    ) %>%
    mutate(
      fr = deaths / confirmed,
      type = "No Offset"
    ),
  confirmed %>%
    dplyr::filter(date <= dt) %>%    
    mutate(
      date = date + offset 
    ) %>%
    group_by(date) %>%
    summarise_at("confirmed", sum) %>%
    left_join(
      deaths %>%
        group_by(date) %>%
        summarise_at("deaths", sum) 
    ) %>%
    dplyr::filter(
      date > minDate
    ) %>%
    mutate(
      fr = deaths / confirmed,
      type = glue("Offset ({offset} days)")
    ) 
) %>%
  bind_rows() %>%
  ggplot(
    aes(date, fr, colour = type)
  ) +
  geom_line() +
  scale_x_date(
    expand = expansion(mult = 0, add = 0)
  ) +
  scale_y_continuous(label = percent) +
  labs(
    x = "Date",
    y = "Estimated Fatality Rate",
    colour = "Calculation"
  )
*Fatality rate for Australian cases as calculated using two methods.
Where no offset is included, the rate shown is simply the number of fatalities divided by the total number of reported cases on the same date.
When cases increase during a new outbreak, this will skew the fatality rate lower.
An alternative is to use an offset based on the fact the the median time from infection to symptom onset is 6 days, the median time from symptom onset to ICU admission is 11 days, and the median time from ICU admission to mortality is 7 days.
When using the offset, the fatality rate is calculated as the number of recorded fatalities on a given date, divided by by the number of cases from 18 days ago.
Whilst still flawed this may give a less biased estimate on the true fatality rate, and importantly, will always be higher than the alternative calculation.
The intial fatality rate spiked above 30% during the intial outbreak under the offset approach, and as such, data is only shown after 20 Apr, 2020.
All times used for estimation the offset were obtained from [here](https://wwwnc.cdc.gov/eid/article/26/6/20-0320_article)*

Fatality rate for Australian cases as calculated using two methods. Where no offset is included, the rate shown is simply the number of fatalities divided by the total number of reported cases on the same date. When cases increase during a new outbreak, this will skew the fatality rate lower. An alternative is to use an offset based on the fact the the median time from infection to symptom onset is 6 days, the median time from symptom onset to ICU admission is 11 days, and the median time from ICU admission to mortality is 7 days. When using the offset, the fatality rate is calculated as the number of recorded fatalities on a given date, divided by by the number of cases from 18 days ago. Whilst still flawed this may give a less biased estimate on the true fatality rate, and importantly, will always be higher than the alternative calculation. The intial fatality rate spiked above 30% during the intial outbreak under the offset approach, and as such, data is only shown after 20 Apr, 2020. All times used for estimation the offset were obtained from here

Current Growth Factor

n <- 14
cp <- glue(
  "*Growth factor for each State/Territory. 
  This value becomes volatile when daily new cases approach zero as is commonly observed in small populations, and at the end stages of an outbreak. 
  __Values are calculated using only locally-acquired cases__.
  In order to try and minimise volatility a {n} day simple moving average was used, in contrast to the 5 day average as advocated [here](https://www.abc.net.au/news/2020-04-10/coronavirus-data-australia-growth-factor-covid-19/12132478).
  This enables assessment of the growth factor over an entire quarantine period.
  If no new cases are observed over this period, the value is not able to be calculated.*"
)
gf <- list(
  local %>%
    dplyr::filter(date <= dt) %>%    
    arrange(date) %>%
    group_by(State) %>%
    mutate(
      new = c(0, diff(local)),
      new_ma = sma(new, n)
    ) %>%
    dplyr::filter(local > 0, !is.na(new_ma)) %>%
    mutate(
      R = c(NA, new_ma[-1] / new_ma[-n()]),
      R = case_when(
        is.nan(R) ~ NA_real_,
        !is.nan(R) ~ R
      )
    ) %>%
    ungroup() %>%
    arrange(State),
  local %>%
    dplyr::filter(date <= dt) %>%
    arrange(date) %>%
    group_by(Country, date) %>%
    summarise_at(vars(local), sum) %>%
    ungroup() %>%
    mutate(
      new = c(0, diff(local)),
      new_ma = sma(new, n)
    ) %>%
    dplyr::filter(local > 0, !is.na(new_ma)) %>%
    mutate(
      R = c(NA, new_ma[-1] / new_ma[-n()]),
      R = case_when(
        is.nan(R) ~ NA_real_,
        !is.nan(R) ~ R
      ),
      State = "All States"
    ) %>%
    arrange(State)
) %>%
  bind_rows() %>%
  dplyr::filter(date > ymd("2020-04-01")) %>%
  ggplot(aes(date, R, colour = State)) +
  geom_ribbon(aes(ymin = 1, ymax = R), alpha = 0.1) +
  geom_hline(yintercept = 1) +
  geom_vline(
    xintercept = ymd("2020-03-22"),
    linetype = 2,
    colour = "grey30"
  ) +
  geom_label(
    aes(label = R),
    data = . %>%
      dplyr::filter(date == max(date)) %>%
      mutate(R = round(R, 2), date = date + 1),
    fill = rgb(1, 1, 1, 0.3),
    show.legend = FALSE,
    nudge_y = 0.3,
    size = 4
  ) +
  labs(
    x = "Date", y = "Growth Factor"
  ) +
  facet_wrap(~State, scales = "free_x") +
  theme(legend.position = "none") +
  coord_cartesian(ylim = c(0.4, 2.5))#2.1))
gf
*Growth factor for each State/Territory. 
This value becomes volatile when daily new cases approach zero as is commonly observed in small populations, and at the end stages of an outbreak. 
__Values are calculated using only locally-acquired cases__.
In order to try and minimise volatility a 14 day simple moving average was used, in contrast to the 5 day average as advocated [here](https://www.abc.net.au/news/2020-04-10/coronavirus-data-australia-growth-factor-covid-19/12132478).
This enables assessment of the growth factor over an entire quarantine period.
If no new cases are observed over this period, the value is not able to be calculated.*

Growth factor for each State/Territory. This value becomes volatile when daily new cases approach zero as is commonly observed in small populations, and at the end stages of an outbreak. Values are calculated using only locally-acquired cases. In order to try and minimise volatility a 14 day simple moving average was used, in contrast to the 5 day average as advocated here. This enables assessment of the growth factor over an entire quarantine period. If no new cases are observed over this period, the value is not able to be calculated.

The current 14 day growth factor is 1.05 which gives considerable cause for concern..

Testing Within Each State

tested %>% 
  left_join(confirmed, by = c("State", "Country", "date") ) %>%
  dplyr::filter(date == dt) %>%
  left_join(ausPops,  by = "State") %>%
  bind_rows(
    tibble(
      State = "National Total",
      date = dt,
      Population = sum(.$Population, na.rm = TRUE),
      confirmed = sum(.$confirmed, na.rm = TRUE),
      tests = sum(.$tests, na.rm = TRUE)
    )
  ) %>%
  mutate(
    `Tests / '000` = round(1e3 * tests / Population, 2),
    Positive = confirmed / tests,
    Negative = 1 - Positive,
    isTotal = grepl("Total", State)
  ) %>%
  dplyr::select(
    State, Population,
    Confirmed = confirmed,
    Tests = tests, 
    contains("000"), 
    ends_with("ive"),
    isTotal
  ) %>%
  arrange(isTotal, desc(`Tests / '000`)) %>%
  dplyr::select(-isTotal) %>%
  dplyr::rename(
    `% Positive Tests` = Positive,
    `% Negative Tests` = Negative
  ) %>%
  mutate_at(
    vars(starts_with("%")), percent, accuracy = 0.01
  ) %>%
  pander(
    justify = "lrrrrrr",
    missing = "",
    caption = glue(
      "*COVID-19 testing scaled by state population size.
      Confirmed cases are assumed to be the tests returning a positive result.
      The current numbers available for some states are a lower limit, and as such, the proportion of the population tested is likely to be higher, as is the proportion of tests returning a negative result.*"
    ),
    emphasize.strong.rows = nrow(.)
  )
COVID-19 testing scaled by state population size. Confirmed cases are assumed to be the tests returning a positive result. The current numbers available for some states are a lower limit, and as such, the proportion of the population tested is likely to be higher, as is the proportion of tests returning a negative result.
State Population Confirmed Tests Tests / ’000 % Positive Tests % Negative Tests
Victoria 6,629,870 20,361 3,830,531 577.8 0.53% 99.47%
New South Wales 8,117,976 4,832 3,947,649 486.3 0.12% 99.88%
South Australia 1,756,494 570 805,697 458.7 0.07% 99.93%
Northern Territory 245,562 73 79,985 325.7 0.09% 99.91%
Australian Capital Territory 428,060 118 134,156 313.4 0.09% 99.91%
Queensland 5,115,451 1,240 1,458,226 285.1 0.09% 99.91%
Tasmania 535,500 234 140,640 262.6 0.17% 99.83%
Western Australia 2,630,557 848 612,889 233 0.14% 99.86%
National Total 25,459,470 28,276 11,009,773 432.4 0.26% 99.74%

R Session Information

R version 4.0.3 (2020-10-10)

Platform: x86_64-pc-linux-gnu (64-bit)

locale: LC_CTYPE=C, LC_NUMERIC=C, LC_TIME=C, LC_COLLATE=C, LC_MONETARY=C, LC_MESSAGES=en_AU.UTF-8, LC_PAPER=en_AU.UTF-8, LC_NAME=C, LC_ADDRESS=C, LC_TELEPHONE=C, LC_MEASUREMENT=en_AU.UTF-8 and LC_IDENTIFICATION=C

attached base packages: stats, graphics, grDevices, utils, datasets, methods and base

other attached packages: QuantTools(v.0.5.7.1), data.table(v.1.13.4), plotly(v.4.9.2.1), pander(v.0.6.3), rvest(v.0.3.6), xml2(v.1.3.2), glue(v.1.4.2), ggrepel(v.0.8.2), scales(v.1.1.1), lubridate(v.1.7.9.2), rlang(v.0.4.9), forcats(v.0.5.0), stringr(v.1.4.0), dplyr(v.1.0.2), purrr(v.0.3.4), readr(v.1.4.0), tidyr(v.1.1.2), tibble(v.3.0.4), ggplot2(v.3.3.2) and tidyverse(v.1.3.0)

loaded via a namespace (and not attached): Rcpp(v.1.0.5), assertthat(v.0.2.1), digest(v.0.6.27), R6(v.2.5.0), cellranger(v.1.1.0), backports(v.1.2.1), reprex(v.0.3.0), evaluate(v.0.14), highr(v.0.8), httr(v.1.4.2), pillar(v.1.4.7), lazyeval(v.0.2.2), curl(v.4.3), readxl(v.1.3.1), rstudioapi(v.0.13), rmarkdown(v.2.5), labeling(v.0.4.2), selectr(v.0.4-2), htmlwidgets(v.1.5.3), munsell(v.0.5.0), broom(v.0.7.2), compiler(v.4.0.3), modelr(v.0.1.8), xfun(v.0.19), pkgconfig(v.2.0.3), htmltools(v.0.5.0), tidyselect(v.1.1.0), fasttime(v.1.0-2), fansi(v.0.4.1), viridisLite(v.0.3.0), crayon(v.1.3.4), dbplyr(v.2.0.0), withr(v.2.3.0), grid(v.4.0.3), jsonlite(v.1.7.2), gtable(v.0.3.0), lifecycle(v.0.2.0), DBI(v.1.1.0), magrittr(v.2.0.1), cli(v.2.2.0), stringi(v.1.5.3), farver(v.2.0.3), fs(v.1.5.0), ellipsis(v.0.3.1), generics(v.0.1.0), vctrs(v.0.3.5), tools(v.4.0.3), Cairo(v.1.5-12.2), hms(v.0.5.3), crosstalk(v.1.1.0.1), yaml(v.2.2.1), colorspace(v.2.0-0), knitr(v.1.30) and haven(v.2.3.1)